#include "stdio.h"
#include "stdint.h"
#include "string.h"
#include "esp_log.h"
#include "config.h"
#include "http_request.h"
#include "ota.h"
#include "api_map.h"
#define TAG "OTA_API"
#define CONFIG_ITEM_NAME "OTA_USER"

typedef struct ota_config
{
    char bin_url[256];
    char version_url[256];
    char web_url[256];
    char user[32];
    char pwd[64];
} ota_config_t;

static struct auto_ota_data
{
    uint8_t bin_url : 1;
    uint8_t version_url : 1;
    uint8_t web_url : 1;
    uint8_t refresh : 1;
    uint8_t ota_bin : 1;
    uint8_t ota_www : 1;
    uint16_t version;               /* 获取版本号 */
    uint32_t last_get_version_tick; /* 获取版本时间 */
} auto_ota_data = {
    .bin_url = 1,
    .version_url = 1,
    .web_url = 1,
    .refresh = 0,
    .ota_bin = 0,
    .ota_www = 0,
    .version = 0,
    .last_get_version_tick = 0,
};

static ota_config_t ota_config = {
    .bin_url = "https://gitee.com/maplerian/esp-bat-pb-idf/raw/master/bin/${version}/app.bin",
    .version_url = "https://gitee.com/maplerian/esp-bat-pb-idf/raw/master/version.txt",
    .web_url = "https://gitee.com/maplerian/esp-bat-pb-idf/raw/master/bin/${version}/www.bin",
    .user = "admin",
    .pwd = "admin",
};

uint8_t ota_save_config(uint8_t *data, uint16_t len)
{
    if (config_set(CONFIG_ITEM_NAME, len, data))
        return config_commit();
    return false;
}

static uint8_t http_get_version_cb(void *handle, uint8_t *data, uint32_t len)
{
    if (!len || !data)
        return 0;
    if (len > 7)
    {
        printf("[Err] Got version content: %s", (char *)data);
        return 1;
    }
    uint16_t version = 0;
    char *pointer = strstr((char *)data, ".");
    if (pointer)
    {
        pointer[0] = 0;
        version = atoi((char *)pointer + 1);
    }
    version |= atoi((char *)data) << 8;
    printf("get ota version:%d.%d cur app version:%d.%d\n",
           version >> 8, version & 0xff,
           ota_default_handle->version >> 8, ota_default_handle->version & 0xff);
    if (auto_ota_data.version != version)
    {
        auto_ota_data.version = version;
        // char *str = (char *)data;
        // len += sprintf(&str[len], "{");
        // len += sprintf(&str[len], "\"api\":\"ota_version\",");
        // len += sprintf(&str[len], "\"data\":");
        // len += dvc_get_result_to(dvc, (uint8_t *)&str[len], size - len);
        // len += sprintf(&str[len], "}");
        // ws_send_msg(data, len);
        // // printf("%s\n", (char *)data);
        // free(data);
        // if (auto_ota_data.version > ota_default_handle->version)
        //     auto_ota_data.ota_bin = 1;
    }
    return 0;
}

static void auto_ota_loop(void *ota, void *user_data)
{
    if (!auto_ota_data.version_url && !auto_ota_data.bin_url && !auto_ota_data.web_url)
        return;
    while (auto_ota_data.version_url)
    {
        struct timeval now;
        gettimeofday(&now, NULL);
        uint32_t tick = now.tv_sec;
        // if (!(auto_ota_data.ota_bin || auto_ota_data.ota_www || auto_ota_data.refresh))
        if (!(auto_ota_data.refresh))
        {
            /* 是否到时间检测 */
            if (tick - auto_ota_data.last_get_version_tick < 60 * 1000) /* 1min */
                break;
            if (tick - auto_ota_data.last_get_version_tick > 24 * 60 * 60 * 1000) /* 1h */
            {
                auto_ota_data.last_get_version_tick = tick;
                break;
            }
        }
        auto_ota_data.last_get_version_tick = tick;
        http_request_from_url("GET", ota_config.version_url, NULL, 0, http_get_version_cb, NULL, NULL);
        auto_ota_data.refresh = 0;
        break;
    }
    if (auto_ota_data.ota_bin && auto_ota_data.bin_url)
    {
        char url[256] = {0};
        strcpy(url, ota_config.bin_url);
        ota_default_handle->checkVersion = 1; /* 0: > 当前版本 1:>=当前版本 */
        if (!auto_ota_data.version_url)
            ota_default_handle->checkVersion = 2; /* 2: 不检测 */
        char *version_idx = strstr(url, "${version}");
        if (version_idx)
        {
            *version_idx = 0;
            if (ota_default_handle->checkVersion != 2)                                                   /* 存在版本号控制 */
                sprintf(version_idx, "%u.%u", auto_ota_data.version >> 8, auto_ota_data.version & 0xff); /* 替换为版本号 */
            strcat(url, ota_config.bin_url + (version_idx - url) + 10);
        }
        /* OTA */
        ota_set_url(ota_default_handle, url, OTA_TYPE_APP);
        auto_ota_data.ota_bin = 0;
    }
    if (auto_ota_data.ota_www && auto_ota_data.web_url)
    {
        char url[256] = {0};
        strcpy(url, ota_config.web_url);
        char *version_idx = strstr(url, "${version}");
        if (version_idx)
        {
            *version_idx = 0;
            if (auto_ota_data.version_url)                                                               /* 存在版本号控制 */
                sprintf(version_idx, "%u.%u", auto_ota_data.version >> 8, auto_ota_data.version & 0xff); /* 替换为版本号 */
            strcat(url, ota_config.web_url + (version_idx - url) + 10);
        }
        ota_set_url(ota_default_handle, url, OTA_TYPE_WWW);
        auto_ota_data.ota_www = 0;
    }
}

static void ota_refresh_version(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (strlen(ota_config.version_url) == 0)
        return ws_reply_data(server, frame, "ota_refresh_version", "No find version url");
    auto_ota_data.refresh = 1;
    while (auto_ota_data.refresh)
        vTaskDelay(pdMS_TO_TICKS(100));
    return ws_reply_status(server, frame, "ota_refresh_version", 0);
}

/**
 *  ota_bin  ==> post url:/upgrade/stream/app
 *  ota_web  ==> post url:/upgrade/stream/www
 *  */

static void ota_bin_from_server(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (strlen(ota_config.bin_url) == 0)
        return ws_reply_data(server, frame, "ota_bin_from_server", "No find bin url");
    auto_ota_data.ota_bin = 1;
    return ws_reply_status(server, frame, "ota_bin_from_server", 0);
}

static void ota_web_from_server(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (strlen(ota_config.web_url) == 0)
        return ws_reply_data(server, frame, "ota_web_from_server", "No find web url");
    auto_ota_data.ota_www = 1;
    return ws_reply_status(server, frame, "ota_web_from_server", 0);
}

static void ota_get(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    char *str = (char *)server->data;
    uint16_t len = 0;
    len += sprintf(&str[len], "{");
    len += sprintf(&str[len], "\"api\":\"%s\",", "ota_get");
    len += sprintf(&str[len], "\"data\":{");
    len += sprintf(&str[len], "\"state\":%u,", ota_default_handle->status);
    len += sprintf(&str[len], "\"err_str\":\"%s\",", ota_default_handle->err_str);
    len += sprintf(&str[len], "\"progress\":%d", ota_default_handle->progress);
    len += sprintf(&str[len], "}}");
    frame->payload = server->data;
    frame->len = len;
}

static void ota_get_config(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    char *str = (char *)server->data;
    uint16_t len = 0;
    len += sprintf(&str[len], "{");
    len += sprintf(&str[len], "\"api\":\"%s\",", "ota_get_config");
    len += sprintf(&str[len], "\"data\":{");
    len += sprintf(&str[len], "\"last\":%lu,", auto_ota_data.last_get_version_tick);
    len += sprintf(&str[len], "\"version\":\"%u.%u\",", auto_ota_data.version >> 8, auto_ota_data.version & 0xff);
    len += sprintf(&str[len], "\"url\":\"%s\",", ota_config.bin_url);
    len += sprintf(&str[len], "\"ver_url\":\"%s\",", ota_config.version_url);
    len += sprintf(&str[len], "\"web_url\":\"%s\",", ota_config.web_url);
    len += sprintf(&str[len], "\"user\":\"%s\",", ota_config.user);
    len += sprintf(&str[len], "\"pwd\":\"%s\"", ota_config.pwd);
    len += sprintf(&str[len], "}}");
    frame->payload = server->data;
    frame->len = len;
}

static void ota_set_config(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (!data || !cJSON_IsObject(data))
        goto ota_set_config_error;

    static const char *const keys[] = {
        "url",
        "ver_url",
        "web_url",
        "user",
        "pwd",
    };
#define keys_len sizeof(keys) / sizeof(keys[0])
    cJSON *obj_list[keys_len] = {0};
    for (uint8_t i = 0; i < keys_len; i++)
    {
        if (!cJSON_HasObjectItem(data, keys[i]))
            goto ota_set_config_error;
        obj_list[i] = cJSON_GetObjectItem(data, keys[i]);
        if (!cJSON_IsString(obj_list[i]))
            goto ota_set_config_error;
    }

    char *set_url = cJSON_GetStringValue(obj_list[0]);
    char *set_ver_url = cJSON_GetStringValue(obj_list[1]);
    char *set_web_url = cJSON_GetStringValue(obj_list[2]);
    char *set_user = cJSON_GetStringValue(obj_list[3]);
    char *set_pwd = cJSON_GetStringValue(obj_list[4]);

    if (strlen(set_url) > 255 || strlen(set_ver_url) > 255 || strlen(set_web_url) > 255)
        goto ota_set_config_error;
    if (strlen(set_user) > 255 || strlen(set_pwd) > 255)
        goto ota_set_config_error;

    auto_ota_data.bin_url = 0;
    auto_ota_data.version_url = 0;
    auto_ota_data.web_url = 0;
    memset(&ota_config, 0, sizeof(ota_config_t));

    strncpy(ota_config.bin_url, set_url, sizeof(ota_config.bin_url) - 1);
    strncpy(ota_config.version_url, set_ver_url, sizeof(ota_config.version_url) - 1);
    strncpy(ota_config.web_url, set_web_url, sizeof(ota_config.web_url) - 1);
    strncpy(ota_config.user, set_user, sizeof(ota_config.user) - 1);
    strncpy(ota_config.pwd, set_pwd, sizeof(ota_config.pwd) - 1);

    if (strlen(set_url) > 0)
        auto_ota_data.bin_url = 1;
    if (strlen(set_ver_url) > 0)
        auto_ota_data.version_url = 1;
    if (strlen(set_web_url) > 0)
        auto_ota_data.web_url = 1;

    if (ota_save_config((uint8_t *)&ota_config, sizeof(ota_config_t)))
        return ws_reply_status(server, frame, "ota_set_config", 0);
    else
        return ws_reply_data(server, frame, "ota_set_config", "Failed to save config");
ota_set_config_error:
    return ws_reply_data(server, frame, "ota_set_config", "Incorrect/incomplete parameters");
}

esp_err_t get_www_partition(httpd_req_t *req)
{
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "POST,OPTIONS");
    const esp_partition_t *par = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "www");
    if (par)
    {
        // httpd_resp_set_hdr(req, "Content-Length", "*");
        http_server_t server = httpd_get_global_user_ctx(req->handle);
        httpd_resp_set_type(req, "application/octet-stream");
        static const uint16_t buf_size = MIN(4096, sizeof(server->data));
        for (uint32_t offset = 0; offset < par->size; offset += buf_size)
        {
            uint32_t r_size = MIN(buf_size, par->size - offset);
            if (esp_partition_read(par, offset, server->data, r_size) == ESP_OK)
                httpd_resp_send_chunk(req, (const char *)server->data, r_size);
        }
    }
    httpd_resp_sendstr_chunk(req, NULL);
    return ESP_OK;
}

static ota_plugin_s ota_check = {
    .name = "auto ota",
    .user_data = &ota_config,
    .loop = auto_ota_loop,
    .next = 0,
};

static void ota_api_init(void)
{
    ota_init();
    uint16_t size = 0;
    if (config_get(CONFIG_ITEM_NAME, (uint8_t *)&ota_config, sizeof(ota_config_t)))
    {
        ESP_LOGI(TAG, "find ota config size %d", size);
        printf("bin url : %s\n", ota_config.bin_url);
        printf("version url : %s\n", ota_config.version_url);
        printf("web url : %s\n", ota_config.web_url);
        printf("user : %s\n", ota_config.user);
        printf("pwd : %s\n", ota_config.pwd);
    }

    ota_add_plugin(ota_default_handle, &ota_check);

    ADD_API_ITEM(ota_get);
    ADD_API_ITEM(ota_get_config);
    ADD_API_ITEM(ota_set_config);
    ADD_API_ITEM(ota_refresh_version);
    ADD_API_ITEM(ota_bin_from_server);
    ADD_API_ITEM(ota_web_from_server);
    /* ota app back */
}
ADD_API(ota_api_init)